package com.scujj.cs6.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.scujj.cs6.client.BaiduWeatherApiClient;
import com.scujj.cs6.common.BusinessException;
import com.scujj.cs6.common.ErrorCode;
import com.scujj.cs6.constant.WeatherConstant;
import com.scujj.cs6.model.dto.WeatherForecastDTO;
import com.scujj.cs6.model.entity.*;
import com.scujj.cs6.model.vo.*;
import com.scujj.cs6.service.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class WeatherServiceImpl implements WeatherService {

    private final IprangeInfoService iprangeInfoService;

    private final ProvincesService provincesService;

    private final DistrictsService districtsService;

    private final CitiesService citiesService;

    private final BaiduWeatherApiClient baiduWeatherApiClient;

    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;

    private final StringRedisTemplate redisTemplate;

    private final ObjectMapper mapper;

    private final WeatherForecastService weatherForecastService;

    private final static Lock lock = new ReentrantLock();

    @Override
    public WeatherAllVo fetchWeatherByAdcode(String adcode) {
        try {
            return getWeatherAllVo(adcode);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public WeatherAllVo fetchWeatherByIp(HttpServletRequest request) {
        IprangeInfo location = iprangeInfoService.getLocation(request);
        if (!location.getArea().isEmpty()) {
            Districts ds = districtsService.getOne(new LambdaQueryWrapper<>(Districts.class)
                    .eq(Districts::getName, location.getArea()));
            try {
                log.info("获取到的区域信息：{}", ds);
                return getWeatherAllVo(ds.getCode());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        if (!location.getCity().isEmpty()) {
            Cities ct = citiesService.getOne(new LambdaQueryWrapper<>(Cities.class)
                    .eq(Cities::getName, location.getCity()));
            try {
                log.info("获取到的区域信息：{}", ct);
                return getWeatherAllVo(ct.getCode());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        Provinces province = provincesService.getOne(new LambdaQueryWrapper<>(Provinces.class)
                .eq(Provinces::getName, location.getProvince()));
        try {
            log.info("获取到的省份信息：{}", province);
            return getWeatherAllVo(province.getCode());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<WeatherNowVo> fetchCapitalCitiesLocalWeather() {
        List<Cities> capitalList = citiesService.getAllProvincialCapital();
        int total = capitalList.size();
        log.info("全国省会城市数量：{}", total);
        List<WeatherNowVo> resultList = Collections.synchronizedList(new ArrayList<>());
        Semaphore semaphore = new Semaphore(1); // 限制并发数为1
        CountDownLatch latch = new CountDownLatch(total); // 等待所有任务完成

        int retryTimes = 3; // 最大重试次数
        for (Cities city : capitalList) {
            threadPoolTaskExecutor.execute(() -> {
                try {
                    semaphore.acquire(); // 获取并发许可
                    try {
                        WeatherNowVo weatherNowVo = fetchWeatherLocalWithRetry(city, retryTimes);
                        if (weatherNowVo != null) {
                            resultList.add(weatherNowVo);
                            log.info("获取{}|{}的天气信息成功", city.getName(), city.getCode());
                        } else {
                            log.error("获取{}|{}的天气信息失败（已重试{}次）", city.getName(), city.getCode(), retryTimes);
                        }
                    } finally {
                        semaphore.release(); // 释放许可
                        latch.countDown(); // 任务完成
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    log.error("线程中断异常", e);
                    latch.countDown();
                }
            });
        }

        try {
            latch.await(); // 等待所有任务完成
            return resultList;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("等待任务完成时中断", e);
        }
    }

    private WeatherNowVo fetchWeatherLocalWithRetry(Cities city, int maxAttempts) {
        int attempt = 0;
        while (attempt < maxAttempts) {
            try {
                WeatherNowVo res = getWeatherNowVo(city.getCode());
                if (res != null) {
                    log.info("获取{}|{}的天气信息成功: {}", city.getName(), city.getCode(),res);
                    return res;
                }
                attempt++;
                if (attempt < maxAttempts) {
                    log.warn("获取{}|{}的天气信息失败，正在进行第{}次重试", city.getName(), city.getCode(), attempt);
                    TimeUnit.SECONDS.sleep(1); // 等待1秒后重试
                }
            } catch (Exception e) {
                log.error("获取{}|{}的天气信息出现异常，第{}次尝试", city.getName(), city.getCode(), attempt + 1, e);
                attempt++;
            }
        }
        log.error("获取{}|{}的天气信息失败，已达到最大重试次数", city.getName(), city.getCode());
        return null;
    }

    @Override
    public List<WeatherForecastDTO> fetchCapitalCitiesForecasts() {
        List<Cities> capitalList = citiesService.getAllProvincialCapital();
        int total = capitalList.size();
        log.info("全国省会城市数量：{}", total);
        List<WeatherForecastDTO> resultList = Collections.synchronizedList(new ArrayList<>());

        Semaphore semaphore = new Semaphore(2); // 限制并发数为2
        CountDownLatch latch = new CountDownLatch(total); // 等待所有任务完成
        int retryTimes = 3; // 最大重试次数

        for (Cities city : capitalList) {
            threadPoolTaskExecutor.execute(() -> {
                try {
                    semaphore.acquire(); // 获取并发许可
                    try {
                        WeatherForecastDTO res = fetchForecastWithRetry(city, 10);
                        if (res != null) {
                            resultList.add(res);
                            log.info("获取{}|{}的天气信息成功", city.getName(), city.getCode());
                        } else {
                            log.error("获取{}|{}的天气信息失败（已重试{}次）", city.getName(), city.getCode(),retryTimes);
                        }
                    } finally {
                        semaphore.release(); // 释放许可
                        latch.countDown(); // 任务完成
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    log.error("线程中断异常", e);
                    latch.countDown();
                }
            });
        }

        try {
            latch.await(); // 等待所有任务完成
            return resultList;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("等待任务完成时中断", e);
        }
    }



    // 带最多重试次数的获取逻辑
    private WeatherForecastDTO fetchForecastWithRetry(Cities city, int maxAttempts) {
        int attempt = 0;
        while (attempt < maxAttempts) {
            try {
                WeatherForecastDTO res = getWeatherForecast(city.getCode());
                if (res != null) {
                    res.setAdcode(city.getCode());
                    return res;
                }
                attempt++;
                if (attempt < maxAttempts) {
                    log.warn("获取{}|{}的天气信息失败，正在进行第{}次重试", city.getName(), city.getCode(), attempt);
                    TimeUnit.SECONDS.sleep(1); // 等一下再重试
                }
            } catch (Exception e) {
                log.error("获取{}|{}的天气信息出现异常，第{}次尝试", city.getName(), city.getCode(), attempt + 1, e);
                attempt++;
            }
        }
        return null;
    }


    @Override
    public RealTimeDataVo<List<WeatherNowVo>> getCapitalCitiesLocalWeather() {
        String jsonStr = redisTemplate.opsForValue().get(WeatherConstant.localCacheKeyPrefix + "all");
        if (jsonStr != null) {
            try {
                log.info("从缓存中获取天气信息: {}",jsonStr);
                return mapper.readValue(jsonStr, new TypeReference<RealTimeDataVo<List<WeatherNowVo>>>() {});
            } catch (JsonProcessingException e) {
                log.error("解析天气信息失败", e);
                return null;
            }
        } else {
            try {
                lock.lock();
                // 再次检查缓存
                String cachedJsonStr = redisTemplate.opsForValue().get(WeatherConstant.localCacheKeyPrefix + "all");
                if (cachedJsonStr != null) {
                    log.info("从缓存中获取天气信息: {}",cachedJsonStr);
                    return mapper.readValue(cachedJsonStr, new TypeReference<RealTimeDataVo<List<WeatherNowVo>>>() {});
                }
                List<WeatherNowVo> weatherNowVos = this.fetchCapitalCitiesLocalWeather();
                RealTimeDataVo<List<WeatherNowVo>> realTimeDataVo = new RealTimeDataVo<>();
                realTimeDataVo.setData(weatherNowVos);
                realTimeDataVo.setUpdateTime(LocalDateTime.now());
                String key = WeatherConstant.localCacheKeyPrefix + "all";
                String json;
                try {
                    json = mapper.writeValueAsString(realTimeDataVo);
                } catch (JsonProcessingException e) {
                    throw new RuntimeException(e);
                }
                redisTemplate.opsForValue().set(key, json, Duration.ofHours(1));
                return realTimeDataVo;
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        }
    }

    private WeatherForecastDTO getWeatherForecast(String districtCode) throws Exception {
        final String jsonStr = baiduWeatherApiClient.fetchForecasts(districtCode);
        JsonNode resultNode = getJsonNode(jsonStr);
        WeatherForecastDTO weatherForecast = mapper.treeToValue(resultNode, WeatherForecastDTO.class);
        if (weatherForecast != null) {
            weatherForecast.setAdcode(districtCode);
        }
        return weatherForecast;
    }

    /**
     * 解析JSON字符串，获取天气数据
     * @param jsonStr
     * @return
     * @throws JsonProcessingException
     */
    private JsonNode getJsonNode(String jsonStr) throws JsonProcessingException {
        return mapper.readTree(jsonStr).get("result");
    }

    /**
     * 通过行政区代码获取当地天气信息
     * @param districtCode
     * @return
     * @throws Exception
     */
    private WeatherNowVo getWeatherNowVo(String districtCode) throws Exception {
        final String jsonStr = baiduWeatherApiClient.fetchNow(districtCode);
        JsonNode resultNode = getJsonNode(jsonStr);
        return mapper.treeToValue(resultNode, WeatherNowVo.class);
    }


    /**
     * 通过行政区代码获取当地天气所有信息
     *
     * @param districtCode 行政区代码
     * @return {@link WeatherHoursVo}
     * @throws Exception
     */
    private WeatherAllVo getWeatherAllVo(String districtCode) throws Exception {
        // 从缓存中获取
        String cacheKey = WeatherConstant.allCacheKeyPrefix + districtCode;
        String weatherCacheJsonStr = redisTemplate.opsForValue().get(cacheKey);
        final ObjectMapper mapper = new ObjectMapper();
        mapper.findAndRegisterModules(); // Ensure Java 8 date/time module is registered
        mapper.setDateFormat(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

        if (weatherCacheJsonStr == null) {
            // 如果缓存中没有，则从API获取
            log.info("缓存中没有天气信息，正在从API获取");
            final String jsonStr = baiduWeatherApiClient.fetchAll(districtCode);
            JsonNode rootNode = getJsonNode(jsonStr);
            WeatherAllVo weatherAllVo = mapper.treeToValue(rootNode, WeatherAllVo.class);
            // 将获取到的天气信息存入缓存
            redisTemplate.opsForValue().set(cacheKey,
                    mapper.writeValueAsString(weatherAllVo),
                    Duration.ofHours(1));
            return weatherAllVo;
        } else {
            // 如果缓存中有，则直接返回
            log.info("从缓存中获取天气信息");
            return mapper.readValue(weatherCacheJsonStr, WeatherAllVo.class);
        }
    }

    @Override
    public PageVo<WeatherForecastWithCitiesVo> pageCapitalCitiesWeatherForecast(int page, int size) {
        PageVo<CitiesVo> citiesPageVo = this.citiesService.pageProvincialCapital(page, size);
        if (citiesPageVo.getRecords().isEmpty()) {
            throw new BusinessException(ErrorCode.NOT_FOUND, "没有更多省会城市天气数据可以获取");
        }

        List<String> adcodeList = citiesPageVo.getRecords().stream().map(CitiesVo::getAdcode).toList();
        int beforeSize = 5;
        List<WeatherForecastWithCities> weatherForecasts = this.weatherForecastService.getForecastWithCitiesByAdCode(adcodeList,beforeSize);
        Map<String, Pair<CitiesVo,List<WeatherForecastVo>>> adcodeMap = citiesPageVo.getRecords().stream()
                .collect(Collectors.toMap(
                        CitiesVo::getAdcode,
                        cityVo -> Pair.of(cityVo, new ArrayList<>()),
                        (existing, replacement) -> existing // 处理 key 冲突
                ));
        log.info("天气预报数据 adcodeMap：{}", adcodeMap);
        weatherForecasts.forEach(weatherForecast -> adcodeMap.get(weatherForecast.getAdcode()).getRight().add(new WeatherForecastVo(weatherForecast)));
        List<WeatherForecastWithCitiesVo> weatherForecastWithCitiesVos = adcodeMap.values().stream()
                .map(pair -> new WeatherForecastWithCitiesVo(pair.getLeft(), pair.getRight()))
                .toList();
        log.info("天气预报数据 weatherForecastWithCitiesVos：{}", weatherForecastWithCitiesVos);
        PageVo<WeatherForecastWithCitiesVo> weatherPage = new PageVo<>();
        weatherPage.setPage(page);
        weatherPage.setSize(size);
        weatherPage.setTotal(citiesPageVo.getTotal());
        weatherPage.setPages(citiesPageVo.getPages());
        weatherPage.setRecords(weatherForecastWithCitiesVos);
        return weatherPage;
    }
}
